#include "atomalgorithms.h"

#include <algorithm>

 // The default constructor. Use setAtoms() to set the list to work on.
Atom::sorter::sorter() : atoms(nullptr), xdirection(true) { }
// Overload constructor, set the atoms and the direction at construction.
Atom::sorter::sorter(Atom::list* const &list, bool xdir) : atoms(list), xdirection(xdir) { }
  
// This setter function set a new reference to the atoms.
void Atom::sorter::setAtoms(Atom::list* const &list) { atoms = list; }
  
// This setter function sets the direction to either x (true) or y (false)
void Atom::sorter::setDir(bool xdir) { xdirection = xdir; }
  
// The functor operator, this will sort the referenced list.
void Atom::sorter::operator()()
{
	static const auto less_than_x = [] (const Atom &a1, const Atom &a2) {
		return (a1.x() == a2.x()) ? a1.y() < a2.y() : a1.x() < a2.x();
	};
	static const auto less_than_y = [] (const Atom &a1, const Atom &a2) {
		return (a1.y() == a2.y()) ? a1.x() < a2.x() : a1.y() < a2.y();
	};

	if(xdirection)
		std::sort(atoms->begin(), atoms->end(), less_than_x);
	else
		std::sort(atoms->begin(), atoms->end(), less_than_y);
}

// This static function is used to et the default filterflags. Default filters all.
Atom::filter::filterfield Atom::filter::defaultFilters() { return floatingatoms | danglingatoms; }

// The default constructor, use setAtoms() and setFilters() to use the function object.
Atom::filter::filter() : atoms(nullptr), periodicity(nullptr), filters(defaultFilters()) { }
// Overload constructor, sets the atoms and the filters at construction.
Atom::filter::filter(Atom::list* const &list, imat* const &per, const filterfield &f) : atoms(list), periodicity(per), filters(f) { }
  
// This setter function sets the reference to the atom list that is filtered.
void Atom::filter::setAtoms(Atom::list* const &list) { atoms = list; }
  
// This setter function set the filters.
void Atom::filter::setFilters(const filterfield &f) { filters = f; }

#include <cilk/reducer_list.h>

// The functor operator, this will filter the referenced atom list.
void Atom::filter::operator()()
{
	if(!atoms || atoms->size() == 0)
		return;

	const size_t origsize = atoms->size();
	Atom::reference::grid atomgrid(atoms,periodicity);
	
	std::vector<size_t> badatoms;
	badatoms.reserve(origsize);

	const double invsqrtthree = 1./sqrt(3.);
	
	for(size_t a = 0; a < origsize; a++)
	{
		(*atoms)[a].setType(Atom::floating);
				
		int neighbours = 0;
		for(long i = -1; i <= 1; i++)
		{
			for(long j = -1; j <= 1; j++)
			{
				const Atom::reference::list &list = atomgrid.getSurrounding((*atoms)[a],i,j);
				size_t subsize = list.size();
				for(size_t k = 0; k < subsize; k++)
				{
					// All nearest neighbours are equidistant from the atom.
					double dist = arma::norm((atoms->at(a).xy()-list[k].xy()),2);
					if((*atoms)[a].h() == list[k].h() && dist > 0.1 && dist < 1.1*invsqrtthree)
					{
						(*atoms)[a].setType(Atom::regular);
						neighbours++;
					}
				}
			}
		}

		if((*atoms)[a].getType() == Atom::floating || neighbours < 2)
			badatoms.push_back(a);
	}

	erase_indices(badatoms);
}

// A small helper function to delete many items in a vector.
void Atom::filter::erase_indices(std::vector<arma::ivec4> &atoms,std::vector<size_t>& indicesToDelete)
{
    if(indicesToDelete.empty())
        return;

    std::vector<arma::ivec4> ret;
    ret.reserve(atoms.size() - indicesToDelete.size());

    std::sort(indicesToDelete.begin(), indicesToDelete.end());

    // now we can assume there is at least 1 element to delete. copy blocks at a time.
    auto itBlockBegin = atoms.begin();
    for(auto it = indicesToDelete.begin(); it != indicesToDelete.end(); ++ it)
    {
        auto itBlockEnd = atoms.begin() + *it;
        if(itBlockBegin != itBlockEnd)
        {
            std::copy(itBlockBegin, itBlockEnd, std::back_inserter(ret));
        }
        itBlockBegin = itBlockEnd + 1;
    }

    // copy last block.
    if(itBlockBegin != atoms.end())
    {
        std::copy(itBlockBegin, atoms.end(), std::back_inserter(ret));
    }

    atoms = ret;
}

// A small helper function to delete many items in a vector.
void Atom::filter::erase_indices(std::vector<arma::ivec4> &atoms,std::list<size_t>& indicesToDelete)
{
    if(indicesToDelete.empty())
        return;

    std::vector<arma::ivec4> ret;
    ret.reserve(atoms.size() - indicesToDelete.size());

	indicesToDelete.sort();

    // now we can assume there is at least 1 element to delete. copy blocks at a time.
    auto itBlockBegin = atoms.begin();
    for(auto it = indicesToDelete.begin(); it != indicesToDelete.end(); ++ it)
    {
        auto itBlockEnd = atoms.begin() + *it;
        if(itBlockBegin != itBlockEnd)
        {
            std::copy(itBlockBegin, itBlockEnd, std::back_inserter(ret));
        }
        itBlockBegin = itBlockEnd + 1;
    }

    // copy last block.
    if(itBlockBegin != atoms.end())
    {
        std::copy(itBlockBegin, atoms.end(), std::back_inserter(ret));
    }

    atoms = ret;
}

void Atom::filter::erase_indices( std::vector<size_t>& indicesToDelete)
{
    if(indicesToDelete.empty())
        return;

    Atom::list ret;
    ret.reserve(atoms->size() - indicesToDelete.size());

    std::sort(indicesToDelete.begin(), indicesToDelete.end());

    // now we can assume there is at least 1 element to delete. copy blocks at a time.
    auto itBlockBegin = atoms->begin();
    for(auto it = indicesToDelete.begin(); it != indicesToDelete.end(); ++ it)
    {
        auto itBlockEnd = atoms->begin() + *it;
        if(itBlockBegin != itBlockEnd)
        {
            std::copy(itBlockBegin, itBlockEnd, std::back_inserter(ret));
        }
        itBlockBegin = itBlockEnd + 1;
    }

    // copy last block.
    if(itBlockBegin != atoms->end())
    {
        std::copy(itBlockBegin, atoms->end(), std::back_inserter(ret));
    }

    *atoms = ret;
}

inline void Atom::filter::erase_indices( std::list<size_t>& indicesToDelete)
{
    if(indicesToDelete.empty())
        return;

    Atom::list ret;
    ret.reserve(atoms->size() - indicesToDelete.size());

    indicesToDelete.sort();

    // now we can assume there is at least 1 element to delete. copy blocks at a time.
    auto itBlockBegin = atoms->begin();
    for(auto it = indicesToDelete.begin(); it != indicesToDelete.end(); ++ it)
    {
        auto itBlockEnd = atoms->begin() + *it;
        if(itBlockBegin != itBlockEnd)
        {
            std::copy(itBlockBegin, itBlockEnd, std::back_inserter(ret));
        }
        itBlockBegin = itBlockEnd + 1;
    }

    // copy last block.
    if(itBlockBegin != atoms->end())
    {
        std::copy(itBlockBegin, atoms->end(), std::back_inserter(ret));
    }

    *atoms = ret;
}